package com.zxd.interview.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.CharsetUtil;
import com.google.common.io.Files;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 文件操作辅助工具类
 *
 * @author zhaoxudong
 * @version v1.0.0
 * @Package : com.zxd.interview.util
 * @Description : 文件操作工具类
 * @Create on : 2021/5/14 10:47
 **/
public class FileUtils {

    /**
     * 拷贝文件操作：
     * 要求：
     * 1. 原始路径不能和拷贝后路径一致（文件不能重复）
     * 2. 默认覆盖文件
     *
     * @param from 被拷贝文件
     * @param to   目标位置
     * @throws IOException
     */
    public static void copyFile(File from, File to) throws IOException {
        PreconditionUtils.checkArgument(!from.equals(to), "原始文件 %s 必须和拷贝后文件 %s 不相同", from, to);
        Files.copy(from, to);
    }

    /**
     * 使用覆盖的形式将数据写入到对应的文件
     * @param from 数据源
     * @param to 目标文件
     * @throws IOException
     */
    public static void write(byte[] from, File to) throws IOException {
        Files.write(from, to);
    }



    /**
     * 剪切文件方法
     * 如果遇到重名的文件，直接覆盖。并且内容也会进行覆盖
     *
     * @param from
     * @param to
     * @throws IOException
     */
    public static void move(File from, File to) throws IOException {
        Files.move(from, to);
    }

    /**
     * 创建一个空文件或更新上次更新的时间戳。
     *
     * @param file 文件
     */
    public static void touch(File file) throws IOException {
        Files.touch(file);
    }

    /**
     * 通过递归创建迭代父类的目录
     * 1. 注意只有路径对应的父目录不存在才会进行创建
     * 2. 不会创建文件，只会创建目录。无副作用
     *
     * @param file
     * @throws IOException
     */
    public static void createParentDirs(File file) throws IOException {
        Files.createParentDirs(file);
    }


    /**
     * <pre>
     *
     * 实现类：不推荐使用。
     * 对于Android用户，请参阅“数据和文件存储”概述以选择适当的临时目录（可能是context.getCacheDir（））。
     * 对于Java 7或更高版本的开发人员，请使用
     * </pre>
     *
     * @return
     */
    public static File createTempDir() {
        return Files.createTempDir();
    }

    /**
     * 获取指定文件名的扩展名称
     *
     * @param fullName 文件名称
     * @return
     */
    public static String getFileExtension(String fullName) {
        return Files.getFileExtension(fullName);
    }

    /**
     * 获取指定文件的扩展名称
     *
     * @param file
     * @return
     */
    public static String getFileExtension(File file) {
        PreconditionUtils.checkArgument(ObjectUtil.nonNull(file), "传入文件对象不能为空");
//        PreconditionUtils.checkArgument(file.exists(), "文件不存在");
        return Files.getFileExtension(file.getName());
    }

    /**
     * 获取文件的名称（不包含扩展名称）
     *
     * @param file 文件对象
     * @return
     */
    public static String getNameWithoutExtension(String file) {
        return Files.getNameWithoutExtension(file);
    }

    /**
     * 获取文件的名称（不包含扩展名称）
     *
     * @param file 文件对象
     * @return
     */
    public static String getNameWithoutExtension(File file) {
        PreconditionUtils.checkArgument(ObjectUtil.nonNull(file), "传入文件对象不能为空");
        return Files.getNameWithoutExtension(file.getName());
    }

    /**
     * 读取文件的第一行
     *
     * @param file    文件
     * @param charset 编码
     * @return
     * @throws IOException
     */
    public static String readFirstLine(File file, Charset charset) throws IOException {
        return Files.readFirstLine(file, charset);
    }

    /**
     * 逐行读取文件内容，并且返回List
     *
     * @param file    文件
     * @param charset 编码
     * @return
     * @throws IOException
     */
    public static List readLines(File file, Charset charset) throws IOException {
        return Files.readLines(file, charset);
    }

    /**
     * 指定文件最后修改时间
     *
     * @param file 文件
     * @return 最后修改时间
     */
    public static Date lastModifiedTime(File file) {
        return FileUtil.lastModifiedTime(file);
    }

    /**
     * 计算目录或文件的总大小<br>
     * 当给定对象为文件时，直接调用 {@link File#length()}<br>
     *
     * @param file 目录或文件
     * @return 总大小，bytes长度
     */
    public static long sizeFile(File file) {
        PreconditionUtils.checkArgument(Objects.nonNull(file), "当前文件对象不能为空");
        PreconditionUtils.checkArgument(file.isFile(), "当前文件为一个目录");
        return FileUtil.size(file);
    }

    /**
     * 计算某个目录下面所有文件的大小
     * 当给定对象为目录时，遍历目录下的所有文件和目录，递归计算其大小，求和返回
     *
     * @param file 文件对象
     * @return
     */
    public static long sizeDir(File file) {
        PreconditionUtils.checkArgument(Objects.nonNull(file), "当前文件对象不能为空");
        PreconditionUtils.checkArgument(file.isDirectory(), "当前路径不是一个文件夹");
        return FileUtil.size(file);
    }

    /**
     * 删除文件或者文件夹<br>
     * 路径如果为相对路径，会转换为ClassPath路径！ 注意：删除文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param file 文件或者目录的路径
     * @return 成功与否
     * @throws IORuntimeException IO异常
     */
    public static boolean delFile(String file) throws IORuntimeException {
        PreconditionUtils.checkArgument(StringUtils.isNoneBlank(file), "当前文件对象不能为空");
        return delFile(new File(file));
    }

    /**
     * 只删除文件，单单进行删除文件的操作
     *
     * @param file 文件
     * @return
     * @throws IORuntimeException
     */
    public static boolean delFile(File file) throws IORuntimeException {
        PreconditionUtils.checkArgument(Objects.nonNull(file), "当前文件对象不能为空");
        return FileUtil.del(file);
    }


    /**
     * 删除文件夹
     * <pre>
     *     1. 文件夹存在递归的文件内容，一并删除
     *     2. 文件夹里面存在内容，一并删除
     * </pre>
     *
     * @param file 文件夹
     * @return
     * @throws IORuntimeException
     */
    public static boolean delDir(File file) throws IORuntimeException {
        PreconditionUtils.checkArgument(Objects.nonNull(file), "当前文件对象不能为空");
        return FileUtil.del(file);
    }

    /**
     * 备注：
     * <pre>
     *     这个方法设计的理解性较差：故改为如下形式：
     *      1. 按照给定名称进行重命名，如果需要重命名待后缀需要自行指定后缀，防止误解API不做处理
     *      2. 拆分为两个方法，一个方法会进行覆盖，但是如果重命名之后存在将不会覆盖
     * </pre>
     * <p>
     * 下面是原作者备注
     * 修改文件或目录的文件名，不变更路径，只是简单修改文件名<br>
     * 重命名有两种模式：<br>
     * 1、isRetainExt为true时，保留原扩展名：
     *
     * <pre>
     * FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png
     * </pre>
     * <p>
     * 2、isRetainExt为false时，不保留原扩展名，需要在newName中
     *
     * <pre>
     * FileUtil.rename(file, "aaa.jpg", false) xx/xx.png =》xx/aaa.jpg
     * </pre>
     *
     * @param file        被修改的文件
     * @param newName     新的文件名，包括扩展名
     * @param isRetainExt 是否保留原文件的扩展名，如果保留，则newName不需要加扩展名
     * @param isOverride  是否覆盖目标文件
     * @return 目标文件
     * @since 3.0.9
     */
    public static File renameOver(File file, String newName) {
        return FileUtil.rename(file, newName, false, true);
    }

    /**
     * 以不覆盖的形式进行处理。
     * <pre>
     * 注意：如果出现重命名冲突，该方法会抛出运行时异常并且强制中断
     *
     * </pre>
     *
     * @param file    被修改的文件
     * @param newName 新的文件名，包括扩展名
     * @return
     * @throws cn.hutool.core.io.IORuntimeException: 如果重命名之后的文件已经存在，则抛出此异常
     */
    public static File renameNonOver(File file, String newName) {
        return FileUtil.rename(file, newName, false, false);
    }
    /**
     * 判断文件是否存在，如果file为null，则返回false
     *
     * @param file 文件
     * @return 如果存在返回true
     */
    public static boolean exist(File file) {
        return (file != null) && file.exists();
    }

    /**
     * 递归遍历所有的文件和目录
     * @param path
     * @return
     */
    public static List<File> loopFiles(String path){
        return FileUtil.loopFiles(path);
    }

    /**
     * 递归遍历所有的文件和目录
     * @param file
     * @return
     */
    public static List<File> loopFiles(File file){
        return FileUtil.loopFiles(file);
    }

    /**
     * 修复路径<br>
     * 如果原路径尾部有分隔符，则保留为标准分隔符（/），否则不保留
     * <ol>
     * <li>1. 统一用 /</li>
     * <li>2. 多个 / 转换为一个 /</li>
     * <li>3. 去除两边空格</li>
     * <li>4. .. 和 . 转换为绝对路径，当..多于已有路径时，直接返回根路径</li>
     * </ol>
     *
     * 栗子：
     *
     * <pre>
     * "/foo//" =》 "/foo/"
     * "/foo/./" =》 "/foo/"
     * "/foo/../bar" =》 "/bar"
     * "/foo/../bar/" =》 "/bar/"
     * "/foo/../bar/../baz" =》 "/baz"
     * "/../" =》 "/"
     * "foo/bar/.." =》 "foo"
     * "foo/../bar" =》 "bar"
     * "foo/../../bar" =》 "bar"
     * "//server/foo/../bar" =》 "/server/bar"
     * "//server/../bar" =》 "/bar"
     * "C:\\foo\\..\\bar" =》 "C:/bar"
     * "C:\\..\\bar" =》 "C:/bar"
     * "~/foo/../bar/" =》 "~/bar/"
     * "~/../bar" =》 "bar"
     * </pre>
     *
     * @param path 原路径
     * @return 修复后的路径
     */
    public static String normalize(String path) {
        return FileUtil.normalize(path);
    }

    /**
     * 校验是否为标准化的绝对路径
     *
     * @param path
     * @return
     */
    public static boolean isNormalizeAbsolutePath(String path) {
        return FileUtil.isAbsolutePath(normalize(path));
    }

    /**
     * 判断是否为目录，如果path为null，则返回false
     *
     * @param path 文件路径
     * @return 如果为目录true
     */
    public static boolean isDirectory(String path){
        return FileUtil.isDirectory(path);
    }

    /**
     * 判断是否为目录，如果path为null，则返回false
     * @param file
     * @return
     */
    public static boolean isDirectory(File file){
        return FileUtil.isDirectory(file);
    }

    /**
     * 清空文件夹<br>
     * 注意：清空文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param directory 文件夹
     * @return 成功与否
     * @throws IORuntimeException IO异常
     * @since 3.0.6
     */
    public static boolean clean(File directory) throws IORuntimeException {
        return FileUtil.clean(directory);
    }

    /**
     * 获取当前系统的换行分隔符
     *
     * <pre>
     * Windows: \r\n
     * Mac: \r
     * Linux: \n
     * </pre>
     *
     * @return 换行符
     */
    public static String getLineSeparator() {
        return System.lineSeparator();

    }

    /**
     * 将String写入文件，覆盖模式，字符集为UTF-8
     *
     * @param content 写入的内容
     * @param path 文件路径
     * @return 写入的文件
     * @throws IORuntimeException IO异常
     */
    public static File writeUtf8String(String content, String path) throws IORuntimeException {
        return FileUtil.writeString(content, path, CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * 将String写入文件，覆盖模式，字符集为UTF-8
     * 内部使用了 BufferedWriter 缓冲实现
     * @param content 写入的内容
     * @param file 文件
     * @return 写入的文件
     * @throws IORuntimeException IO异常
     */
    public static File writeUtf8String(String content, File file) throws IORuntimeException {
        return FileUtil.writeString(content, file, CharsetUtil.CHARSET_UTF_8);
    }
}
